'''
Class to handle IP addresses

Written by: Julian Tosh
Initial Release: 2008-01-05

The class will render pieces of the address and mask in
various formats such as Integer, Dotted Decimal. Calculations
will be made for subnet ids, broadcast addresses and provide
methods to validate specific kinds of addresses such as
bogons, public and private subnets.

'''

class ipaddress:
	'''
	Instantiate the class with a valid IP and optional mask.
	'''
	__slots__ = ['ipAsDotDec',          # "xxx.xxx.xxx.xxx"
				 'ipAsInt',             # ipDotDec represented as an integer
				 'ipMaskAsDotDec',      # "xxx.xxx.xxx.xxx"
				 'ipMaskAsInt',         # ipMask represented as the number of bits (integer)
				 'ipMaskBits',          # ipMask represented as an integer
				 'ipSubnetAsDotDec',    # Calculated subnet of ip as DotDec
				 'ipSubnetAsInt',       # Calculated subnet of ip as integer
				 'ipBroadcastAsDotDec', # Calculated broadcast of ip as DotDec
				 'ipBroadcastAsInt']    # Calculated broadcast of ip as integer
	
	def __init__(self, ip, mask = 32):
		#
	    # Setup all my public variables
	    #
		self.ipAsDotDec = ""        
		self.ipAsInt = -1           
		
		self.ipMaskAsDotDec = ""    
		self.ipMaskAsInt = -1      
		self.ipMaskBits = -1       

		self.ipSubnetAsDotDec = ""  
		self.ipSubnetAsInt = -1     

		self.ipBroadcastAsDotDec = ""    
		self.ipBroadcastAsInt = -1      
		

		# IP must be a string or integer
		if type(ip)==type(str()):
			# If IP is a string, try and find a CIDR prefix
			try:
				# Check if IP and Mask are in the same string parameter
				(ip, mask) = ip.split("/")
			except:
				pass

			self.ipAsDotDec = ip
			self.ipAsInt = self._dotDecToInt(self.ipAsDotDec)
		else:
			# Assume ip is an integer
			self.ipAsInt = long(ip) & 0xFFFFFFFFL
			self.ipAsDotDec = self._intToDotDec(self.ipAsInt)
		
		# Mask must be string or integer
		if type(mask)==type(int()):
			self.ipMaskBits = mask
			self.ipMaskAsDotDec = self._maskBitsToDotDec(self.ipMaskBits)
			self.ipMaskAsInt = self._dotDecToInt(self.ipMaskAsDotDec)			
		else:
			mask = mask.strip()			
			if len(mask) <= 2:
				self.ipMaskBits = int(mask)
				self.ipMaskAsDotDec = self._maskBitsToDotDec(self.ipMaskBits)
				self.ipMaskAsInt = self._dotDecToInt(self.ipMaskAsDotDec)			
			else:
				self.ipMaskAsDotDec = mask
				self.ipMaskBits = self._maskDotDecToBits(self.ipMaskAsDotDec)
				self.ipMaskAsInt = self._dotDecToInt(self.ipMaskAsDotDec)			
					
		#
		# Setup remaining private variables
		#

		self.ipHostAsInt = long(self.ipAsInt & ~self.ipMaskBits)

		self.ipSubnetAsInt = long( self.ipAsInt & self.ipMaskAsInt )
		self.ipSubnetAsDotDec = self._intToDotDec(self.ipSubnetAsInt)

		self.ipBroadcastAsInt = self.ipSubnetAsInt + ( 0xFFFFFFFFL >> (32 - self.ipMaskBits)) & 0xFFFFFFFFL
		self.ipBroadcastAsDotDec = self._intToDotDec(self.ipBroadcastAsInt)  


###
### Private Functions
###
	def __repr__(self):
		return (
   			    "============================================\n" +
   				"DotDec".center(17)                       + "Hex".center(9)                                        + "Int".center(14)                           + "Bits".ljust(5) + "\n" +
				"I " + self.ipAsDotDec.ljust(16)          + str(hex(self.ipAsInt))[2:-1].upper().rjust(8)          + " " + str(self.ipAsInt).rjust(12)          + "\n" +
				"S " + self.ipSubnetAsDotDec.ljust(16)    + str(hex(self.ipSubnetAsInt))[2:-1].upper().rjust(8)    + " " + str(self.ipSubnetAsInt).rjust(12)    + "\n" +
				"B " + self.ipBroadcastAsDotDec.ljust(16) + str(hex(self.ipBroadcastAsInt))[2:-1].upper().rjust(8) + " " + str(self.ipBroadcastAsInt).rjust(12) + "\n" +
				"M " + self.ipMaskAsDotDec.ljust(16)      + str(hex(self.ipMaskAsInt))[2:-1].upper().rjust(8)      + " " + str(self.ipMaskAsInt).rjust(12)      + " " + str(self.ipMaskBits).ljust(5) + "\n"
			    )
	 
	 
	def __str__(self):
		return self.ipAsDotDec + "/" + str(self.ipMaskBits)


	def _maskBitsToDotDec(self, maskBits):
		'''
		Convert number of bits in mask to a DotDec mask
		'''
		ipMaskInt = ( 0xFFFFFFFFL << (32 - maskBits))
		ipMaskInt = ( 0xFFFFFFFFL & ipMaskInt ) 		
		#print "test: " + str(hex(ipMaskInt))[2:-1].upper()
		return self._intToDotDec(ipMaskInt)

	
	def _maskDotDecToBits(self, maskDotDec):
		'''
		Convert a DotDec string mask to an int
		'''
		ipMaskInt = self._dotDecToInt(maskDotDec)
		bits = 32
		#while ( ipMaskInt & 0x00000001L == 0):
		while (bits >= 0):
			if (ipMaskInt & 0x00000001L):
				break
			ipMaskInt = ipMaskInt >> 1
			bits -= 1
		return bits

		
	def _dotDecToInt(self, string):
		'''
		Converts a DotDec ip address or mask to an integer representation. 
		'''
		if self.isValidAddress(string):
			octet = string.split(".", 4)
			return long (
			    (long(octet[0]) << 24) +
			    (long(octet[1]) << 16) +
			    (long(octet[2]) <<  8) +
			    (long(octet[3]))
			    ) & 0xFFFFFFFFL
		else:
			return long(0)


	def _intToDotDec(self, ipInt):
		ipInt = ( 0xFFFFFFFFL & ipInt ) 
		return (
			    str((ipInt & 0xFF000000L) >> 24) + "." +
			    str((ipInt & 0x00FF0000L) >> 16) + "." +
			    str((ipInt & 0x0000FF00L) >> 8 ) + "." +
			    str( ipInt & 0x000000FFL)
			    )













###
### Public Functions to access private variables
###
	def isValidAddress(self, addressString = ""):
		'''
		Returns true if format of string is in a valid format 
		(i.e. 0-255 [.] 0-255 [.] 0-255 [.]  0-255 ) 
		'''
		if len(addressString) == 0:
			addressString = self.ipAsDotDec
			
		octet = addressString.split(".", 4)

		if len(octet) != 4:
			return False
		else:
			#for each in octet: print each + str((int(each) >= 0) & (int(each) <= 255))  
			return (
					((int(octet[0]) >= 0) & (int(octet[0]) <= 255)) & 
			    	((int(octet[1]) >= 0) & (int(octet[1]) <= 255)) & 
			    	((int(octet[2]) >= 0) & (int(octet[2]) <= 255)) & 
			    	((int(octet[3]) >= 0) & (int(octet[3]) <= 255))
			    	)
	
	def isSubnet(self):
		'''
		Returns true if the IP address is a subnet address
		'''
		if (self.ipMaskBits == 32):
			return False
		else:
			return ( (self.ipAsInt & ~self.ipMaskBits) == 0)
	
	def isBroadcast(self):
		'''
		Returns true if the IP address is a broadcast address
		'''
		if self.ipMaskBits == 32:
			return False
		else:
			return ((~self.ipMaskBits & 0xFFFFFFFFL) == self.getHost())
	
	def isBogon(self):
		'''
		Checks RFC rules to see if it's a bogus address
		'''
		bogonIPSpace = [
					    [self._dotDecToInt("0.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("2.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("5.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("10.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("14.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("23.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("27.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("31.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("36.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("37.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("39.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("42.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("46.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("49.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("50.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("100.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("101.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("102.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("103.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("104.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("105.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("106.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("107.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("108.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("109.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("110.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("111.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("127.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("168.254.0.0"), 0xFFFF0000L],
					    [self._dotDecToInt("172.16.0.0"), 0xFFF00000L],
					    [self._dotDecToInt("175.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("176.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("177.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("178.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("179.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("180.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("181.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("182.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("183.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("184.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("185.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("192.0.2.0"), 0xFFFF0000L],
					    [self._dotDecToInt("192.168.0.0"), 0xFFFF0000L],
					    [self._dotDecToInt("198.18.0.0"), 0xFFFE0000L],
					    [self._dotDecToInt("223.0.0.0"), 0xFF000000L],
					    [self._dotDecToInt("224.0.0.0"), 0xE0000000L]
					    ]
	
		for subnet,mask in bogonIPSpace:
			match = ((self.ipAsInt & mask) == subnet)
			if match: break
		
		return match 


	def isPrivate(self):
		'''
		Returns true if IP address falls within private address space
		'''
		
		#
		# Define private address spaces
		#
		privateIPSpace = [
								[self._dotDecToInt("10.0.0.0"), 0xFF000000L],
								[self._dotDecToInt("172.16.0.0"), 0xFFF00000L],
								[self._dotDecToInt("192.168.0.0"), 0xFFFF0000L]
						]
		
		for subnet,mask in privateIPSpace:
			match = ((self.ipAsInt & mask) == subnet)
			if match: break
		
		return match 
		 
		 
	def isPublic(self):
		if self.isPrivate() == True:
			return False
		else:
			return True

	def isIn(self, ipAddress):
		return ( ( self.ipAsInt & ipAddress.ipMaskAsInt ) == ipAddress.ipSubnetAsInt )  








###
### Module Debug Tests
###

if __name__ == '__main__':
	test = ipaddress("192.168.20.132", "255.255.255.255")
	print test.__repr__()
    
	test = ipaddress("192.168.20.132", "255.255.255.128")
	print test.__repr__()
    
	test = ipaddress("10.0.0.0/8")
	print test.__repr__()
	
	test = ipaddress("10.0.0.1/8")
	print test.__repr__()
		
	test = ipaddress("10.255.255.254/8")
	print test.__repr__()
	
	test = ipaddress("10.255.255.255/8")
	print test.__repr__()
	
	test = ipaddress("172.16.0.0/12")
	print test.__repr__()
	
	test = ipaddress("172.16.0.1/12")
	print test.__repr__()
	
	test = ipaddress("172.31.255.254/12")
	print test.__repr__()
	
	test = ipaddress("172.31.255.255/12")
	print test.__repr__()

	test = ipaddress("172.18.28.255", 12)
	print test.__repr__()

	test = ipaddress("10.0.0.0", 24)
	print test.__repr__()

	test2 = ipaddress("10.0.0.2", 24)
	print test.__repr__()
	print test2.ipAsDotDec + '/' + str(test2.ipMaskBits) + ' in ' + test.ipAsDotDec + '/' + str(test.ipMaskBits) + ': ' + str(test2.isIn(test))

	test2 = ipaddress("10.0.1.2", 24)
	print test.__repr__()
	print test2.ipAsDotDec + '/' + str(test2.ipMaskBits) + ' in ' + test.ipAsDotDec + '/' + str(test.ipMaskBits) + ': ' + str(test2.isIn(test))

	test = ipaddress("10.0.0.0", 8)
	print test.__repr__()
	print test2.ipAsDotDec + '/' + str(test2.ipMaskBits) + ' in ' + test.ipAsDotDec + '/' + str(test.ipMaskBits) + ': ' + str(test2.isIn(test))

